#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
INI文件处理器

该模块提供了INI配置文件的读写和处理功能。

创建时间: 2023-07-12
作者: Python Tools Team
"""

import configparser
from typing import Optional, Union, Dict, Any, List, Tuple, Set
from pathlib import Path
import io

from modules.logger import LogManager
from modules.exception import exception_handler, FileException
from modules.fileio.file_manager import FileManager


class INIFileHandler:
    """
    INI文件处理器
    
    该类提供了INI配置文件的读写和处理功能，支持多种格式和选项。
    """
    
    def __init__(self, file_manager: Optional[FileManager] = None, logger=None):
        """
        初始化INI文件处理器
        
        Args:
            file_manager: 文件管理器实例，如果为None则创建新实例
            logger: 日志记录器，如果为None则使用默认日志记录器
        """
        self.file_manager = file_manager or FileManager()
        self.logger = logger or LogManager.get_both_logger(name="ini_handler")
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def read(self, file_path: Union[str, Path], encoding: str = 'utf-8') -> configparser.ConfigParser:
        """
        读取INI文件
        
        Args:
            file_path: INI文件路径
            encoding: 文件编码，默认为utf-8
        
        Returns:
            configparser.ConfigParser: 配置解析器对象
        
        Raises:
            FileException: 文件不存在或读取失败时抛出
            configparser.Error: INI解析失败时抛出
        
        Examples:
            >>> ini_handler = INIFileHandler()
            >>> config = ini_handler.read('config.ini')
            >>> print(config['DEFAULT']['ServerAliveInterval'])
        """
        file_path = Path(file_path)
        if not file_path.exists():
            raise FileException(f"文件不存在: {file_path}")
        
        config = configparser.ConfigParser()
        
        try:
            config.read(file_path, encoding=encoding)
        except configparser.Error as e:
            self.logger.error(f"INI解析失败: {e}")
            raise
        except Exception as e:
            raise FileException(f"读取INI文件失败: {e}")
        
        self.logger.debug(f"已读取INI文件: {file_path}")
        return config
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def write(self, file_path: Union[str, Path], config: configparser.ConfigParser, 
             encoding: str = 'utf-8', space_around_delimiters: bool = True, 
             create_dirs: bool = True) -> bool:
        """
        写入配置到INI文件
        
        Args:
            file_path: INI文件路径
            config: 配置解析器对象
            encoding: 文件编码，默认为utf-8
            space_around_delimiters: 是否在分隔符周围添加空格，默认为True
            create_dirs: 如果父目录不存在，是否创建，默认为True
        
        Returns:
            bool: 写入成功返回True
        
        Raises:
            FileException: 写入失败时抛出
        
        Examples:
            >>> ini_handler = INIFileHandler()
            >>> config = configparser.ConfigParser()
            >>> config['DEFAULT'] = {'ServerAliveInterval': '45'}
            >>> ini_handler.write('config.ini', config)
        """
        file_path = Path(file_path)
        
        # 确保父目录存在
        if create_dirs:
            file_path.parent.mkdir(parents=True, exist_ok=True)
        
        try:
            with open(file_path, 'w', encoding=encoding) as f:
                config.write(f, space_around_delimiters=space_around_delimiters)
        except Exception as e:
            raise FileException(f"写入INI文件失败: {e}")
        
        self.logger.debug(f"已写入INI文件: {file_path}")
        return True
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def to_dict(self, config: configparser.ConfigParser, 
               include_defaults: bool = True) -> Dict[str, Dict[str, str]]:
        """
        将配置解析器对象转换为字典
        
        Args:
            config: 配置解析器对象
            include_defaults: 是否包含DEFAULT部分，默认为True
        
        Returns:
            Dict[str, Dict[str, str]]: 转换后的字典
        
        Examples:
            >>> ini_handler = INIFileHandler()
            >>> config = ini_handler.read('config.ini')
            >>> config_dict = ini_handler.to_dict(config)
            >>> print(config_dict['section']['option'])
        """
        result = {}
        
        for section in config.sections():
            result[section] = {}
            for option in config[section]:
                result[section][option] = config[section][option]
        
        # 添加DEFAULT部分
        if include_defaults and 'DEFAULT' in config:
            result['DEFAULT'] = {}
            for option in config['DEFAULT']:
                result['DEFAULT'][option] = config['DEFAULT'][option]
        
        return result
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def from_dict(self, data: Dict[str, Dict[str, str]]) -> configparser.ConfigParser:
        """
        将字典转换为配置解析器对象
        
        Args:
            data: 字典数据
        
        Returns:
            configparser.ConfigParser: 配置解析器对象
        
        Examples:
            >>> ini_handler = INIFileHandler()
            >>> data = {
            >>>     'DEFAULT': {'ServerAliveInterval': '45'},
            >>>     'bitbucket.org': {'User': 'hg'}
            >>> }
            >>> config = ini_handler.from_dict(data)
            >>> ini_handler.write('config.ini', config)
        """
        config = configparser.ConfigParser()
        
        for section, options in data.items():
            if section != 'DEFAULT':
                config[section] = {}
            
            for option, value in options.items():
                config[section][option] = str(value)
        
        return config
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def get_sections(self, config: configparser.ConfigParser) -> List[str]:
        """
        获取所有节名称
        
        Args:
            config: 配置解析器对象
        
        Returns:
            List[str]: 节名称列表
        
        Examples:
            >>> ini_handler = INIFileHandler()
            >>> config = ini_handler.read('config.ini')
            >>> sections = ini_handler.get_sections(config)
            >>> print(sections)
        """
        return config.sections()
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def get_options(self, config: configparser.ConfigParser, section: str) -> List[str]:
        """
        获取指定节中的所有选项名称
        
        Args:
            config: 配置解析器对象
            section: 节名称
        
        Returns:
            List[str]: 选项名称列表
        
        Raises:
            configparser.NoSectionError: 节不存在时抛出
        
        Examples:
            >>> ini_handler = INIFileHandler()
            >>> config = ini_handler.read('config.ini')
            >>> options = ini_handler.get_options(config, 'bitbucket.org')
            >>> print(options)
        """
        if section not in config:
            raise configparser.NoSectionError(section)
        
        return list(config[section].keys())
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def has_section(self, config: configparser.ConfigParser, section: str) -> bool:
        """
        检查是否存在指定节
        
        Args:
            config: 配置解析器对象
            section: 节名称
        
        Returns:
            bool: 存在返回True，否则返回False
        
        Examples:
            >>> ini_handler = INIFileHandler()
            >>> config = ini_handler.read('config.ini')
            >>> if ini_handler.has_section(config, 'bitbucket.org'):
            >>>     print('Section exists')
        """
        return config.has_section(section)
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def has_option(self, config: configparser.ConfigParser, section: str, option: str) -> bool:
        """
        检查指定节中是否存在指定选项
        
        Args:
            config: 配置解析器对象
            section: 节名称
            option: 选项名称
        
        Returns:
            bool: 存在返回True，否则返回False
        
        Examples:
            >>> ini_handler = INIFileHandler()
            >>> config = ini_handler.read('config.ini')
            >>> if ini_handler.has_option(config, 'bitbucket.org', 'User'):
            >>>     print('Option exists')
        """
        return config.has_option(section, option)
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def get_value(self, config: configparser.ConfigParser, section: str, option: str, 
                fallback: Any = None) -> str:
        """
        获取选项值
        
        Args:
            config: 配置解析器对象
            section: 节名称
            option: 选项名称
            fallback: 如果选项不存在，返回的默认值
        
        Returns:
            str: 选项值
        
        Examples:
            >>> ini_handler = INIFileHandler()
            >>> config = ini_handler.read('config.ini')
            >>> value = ini_handler.get_value(config, 'bitbucket.org', 'User', 'default')
            >>> print(value)
        """
        return config.get(section, option, fallback=fallback)
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def get_int(self, config: configparser.ConfigParser, section: str, option: str, 
              fallback: Optional[int] = None) -> int:
        """
        获取整数选项值
        
        Args:
            config: 配置解析器对象
            section: 节名称
            option: 选项名称
            fallback: 如果选项不存在或无法转换为整数，返回的默认值
        
        Returns:
            int: 整数选项值
        
        Examples:
            >>> ini_handler = INIFileHandler()
            >>> config = ini_handler.read('config.ini')
            >>> value = ini_handler.get_int(config, 'server', 'port', 8080)
            >>> print(value)
        """
        return config.getint(section, option, fallback=fallback)
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def get_float(self, config: configparser.ConfigParser, section: str, option: str, 
                fallback: Optional[float] = None) -> float:
        """
        获取浮点数选项值
        
        Args:
            config: 配置解析器对象
            section: 节名称
            option: 选项名称
            fallback: 如果选项不存在或无法转换为浮点数，返回的默认值
        
        Returns:
            float: 浮点数选项值
        
        Examples:
            >>> ini_handler = INIFileHandler()
            >>> config = ini_handler.read('config.ini')
            >>> value = ini_handler.get_float(config, 'server', 'timeout', 5.0)
            >>> print(value)
        """
        return config.getfloat(section, option, fallback=fallback)
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def get_boolean(self, config: configparser.ConfigParser, section: str, option: str, 
                  fallback: Optional[bool] = None) -> bool:
        """
        获取布尔选项值
        
        Args:
            config: 配置解析器对象
            section: 节名称
            option: 选项名称
            fallback: 如果选项不存在或无法转换为布尔值，返回的默认值
        
        Returns:
            bool: 布尔选项值
        
        Examples:
            >>> ini_handler = INIFileHandler()
            >>> config = ini_handler.read('config.ini')
            >>> value = ini_handler.get_boolean(config, 'server', 'debug', False)
            >>> print(value)
        """
        return config.getboolean(section, option, fallback=fallback)
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def set_value(self, config: configparser.ConfigParser, section: str, option: str, 
                value: Any) -> configparser.ConfigParser:
        """
        设置选项值
        
        Args:
            config: 配置解析器对象
            section: 节名称
            option: 选项名称
            value: 选项值
        
        Returns:
            configparser.ConfigParser: 更新后的配置解析器对象
        
        Examples:
            >>> ini_handler = INIFileHandler()
            >>> config = ini_handler.read('config.ini')
            >>> config = ini_handler.set_value(config, 'server', 'debug', 'true')
            >>> ini_handler.write('config.ini', config)
        """
        if section not in config:
            config.add_section(section)
        
        config[section][option] = str(value)
        return config
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def remove_section(self, config: configparser.ConfigParser, 
                     section: str) -> configparser.ConfigParser:
        """
        移除节
        
        Args:
            config: 配置解析器对象
            section: 节名称
        
        Returns:
            configparser.ConfigParser: 更新后的配置解析器对象
        
        Examples:
            >>> ini_handler = INIFileHandler()
            >>> config = ini_handler.read('config.ini')
            >>> config = ini_handler.remove_section(config, 'bitbucket.org')
            >>> ini_handler.write('config.ini', config)
        """
        config.remove_section(section)
        return config
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def remove_option(self, config: configparser.ConfigParser, section: str, 
                    option: str) -> configparser.ConfigParser:
        """
        移除选项
        
        Args:
            config: 配置解析器对象
            section: 节名称
            option: 选项名称
        
        Returns:
            configparser.ConfigParser: 更新后的配置解析器对象
        
        Examples:
            >>> ini_handler = INIFileHandler()
            >>> config = ini_handler.read('config.ini')
            >>> config = ini_handler.remove_option(config, 'bitbucket.org', 'User')
            >>> ini_handler.write('config.ini', config)
        """
        config.remove_option(section, option)
        return config
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def merge(self, configs: List[configparser.ConfigParser], 
             override: bool = True) -> configparser.ConfigParser:
        """
        合并多个配置解析器对象
        
        Args:
            configs: 配置解析器对象列表
            override: 是否覆盖已存在的选项，默认为True
        
        Returns:
            configparser.ConfigParser: 合并后的配置解析器对象
        
        Examples:
            >>> ini_handler = INIFileHandler()
            >>> config1 = ini_handler.read('config1.ini')
            >>> config2 = ini_handler.read('config2.ini')
            >>> merged = ini_handler.merge([config1, config2])
            >>> ini_handler.write('merged.ini', merged)
        """
        if not configs:
            return configparser.ConfigParser()
        
        # 使用第一个配置作为基础
        result = configparser.ConfigParser()
        
        # 合并所有配置
        for config in configs:
            for section in config.sections():
                if not result.has_section(section):
                    result.add_section(section)
                
                for option in config[section]:
                    if override or not result.has_option(section, option):
                        result[section][option] = config[section][option]
            
            # 合并DEFAULT部分
            for option in config['DEFAULT']:
                if override or option not in result['DEFAULT']:
                    result['DEFAULT'][option] = config['DEFAULT'][option]
        
        return result
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def merge_files(self, file_paths: List[Union[str, Path]], output_path: Union[str, Path], 
                   encoding: str = 'utf-8', override: bool = True) -> bool:
        """
        合并多个INI文件
        
        Args:
            file_paths: INI文件路径列表
            output_path: 输出文件路径
            encoding: 文件编码，默认为utf-8
            override: 是否覆盖已存在的选项，默认为True
        
        Returns:
            bool: 合并成功返回True
        
        Raises:
            FileException: 合并失败时抛出
        
        Examples:
            >>> ini_handler = INIFileHandler()
            >>> ini_handler.merge_files(['config1.ini', 'config2.ini'], 'merged.ini')
        """
        configs = []
        
        for file_path in file_paths:
            file_path = Path(file_path)
            if not file_path.exists():
                self.logger.warning(f"文件不存在，跳过: {file_path}")
                continue
            
            try:
                config = self.read(file_path, encoding=encoding)
                configs.append(config)
            except Exception as e:
                self.logger.warning(f"读取文件失败，跳过: {file_path}, 错误: {e}")
        
        if not configs:
            raise FileException("没有有效的INI文件可合并")
        
        merged = self.merge(configs, override=override)
        return self.write(output_path, merged, encoding=encoding)
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def filter_sections(self, config: configparser.ConfigParser, 
                      include_sections: List[str] = None, 
                      exclude_sections: List[str] = None) -> configparser.ConfigParser:
        """
        筛选配置中的节
        
        Args:
            config: 配置解析器对象
            include_sections: 要包含的节列表，如果为None则包含所有节
            exclude_sections: 要排除的节列表，如果为None则不排除任何节
        
        Returns:
            configparser.ConfigParser: 筛选后的配置解析器对象
        
        Examples:
            >>> ini_handler = INIFileHandler()
            >>> config = ini_handler.read('config.ini')
            >>> filtered = ini_handler.filter_sections(config, include_sections=['server', 'client'])
            >>> ini_handler.write('filtered.ini', filtered)
        """
        result = configparser.ConfigParser()
        
        # 复制DEFAULT部分
        for option in config['DEFAULT']:
            result['DEFAULT'][option] = config['DEFAULT'][option]
        
        # 筛选节
        for section in config.sections():
            if (include_sections is None or section in include_sections) and \
               (exclude_sections is None or section not in exclude_sections):
                result[section] = {}
                for option in config[section]:
                    result[section][option] = config[section][option]
        
        return result
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def filter_options(self, config: configparser.ConfigParser, section: str, 
                     include_options: List[str] = None, 
                     exclude_options: List[str] = None) -> configparser.ConfigParser:
        """
        筛选指定节中的选项
        
        Args:
            config: 配置解析器对象
            section: 节名称
            include_options: 要包含的选项列表，如果为None则包含所有选项
            exclude_options: 要排除的选项列表，如果为None则不排除任何选项
        
        Returns:
            configparser.ConfigParser: 筛选后的配置解析器对象
        
        Raises:
            configparser.NoSectionError: 节不存在时抛出
        
        Examples:
            >>> ini_handler = INIFileHandler()
            >>> config = ini_handler.read('config.ini')
            >>> filtered = ini_handler.filter_options(config, 'server', include_options=['host', 'port'])
            >>> ini_handler.write('filtered.ini', filtered)
        """
        if section not in config:
            raise configparser.NoSectionError(section)
        
        result = configparser.ConfigParser()
        
        # 复制DEFAULT部分
        for option in config['DEFAULT']:
            result['DEFAULT'][option] = config['DEFAULT'][option]
        
        # 复制其他节
        for sec in config.sections():
            if sec != section:
                result[sec] = {}
                for option in config[sec]:
                    result[sec][option] = config[sec][option]
            else:
                # 筛选指定节中的选项
                result[section] = {}
                for option in config[section]:
                    if (include_options is None or option in include_options) and \
                       (exclude_options is None or option not in exclude_options):
                        result[section][option] = config[section][option]
        
        return result
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def to_string(self, config: configparser.ConfigParser, 
                 space_around_delimiters: bool = True) -> str:
        """
        将配置解析器对象转换为字符串
        
        Args:
            config: 配置解析器对象
            space_around_delimiters: 是否在分隔符周围添加空格，默认为True
        
        Returns:
            str: INI格式字符串
        
        Examples:
            >>> ini_handler = INIFileHandler()
            >>> config = ini_handler.read('config.ini')
            >>> ini_str = ini_handler.to_string(config)
            >>> print(ini_str)
        """
        string_io = io.StringIO()
        config.write(string_io, space_around_delimiters=space_around_delimiters)
        return string_io.getvalue()
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def from_string(self, ini_str: str) -> configparser.ConfigParser:
        """
        将INI格式字符串转换为配置解析器对象
        
        Args:
            ini_str: INI格式字符串
        
        Returns:
            configparser.ConfigParser: 配置解析器对象
        
        Raises:
            configparser.Error: INI解析失败时抛出
        
        Examples:
            >>> ini_handler = INIFileHandler()
            >>> ini_str = "[server]\nhost = localhost\nport = 8080"
            >>> config = ini_handler.from_string(ini_str)
            >>> print(config['server']['port'])  # 8080
        """
        config = configparser.ConfigParser()
        
        try:
            config.read_string(ini_str)
        except configparser.Error as e:
            self.logger.error(f"INI解析失败: {e}")
            raise
        
        return config